#if !defined(E2E_PROCESSOR_H_)
#define E2E_PROCESSOR_H_
#include <boost/json.hpp>
#include <thread>

#include "common/file_helper.h"
#include "common/perception_defs.h"
#include "detector/e2e_net.h"

namespace autodrive {
namespace perception {
class E2EProcessor final {
 public:
  E2EProcessor(E2EProcessor &&) = delete;
  E2EProcessor(const E2EProcessor &) = delete;
  E2EProcessor &operator=(E2EProcessor &&) = delete;
  E2EProcessor &operator=(const E2EProcessor &) = delete;
  ~E2EProcessor();

 public:
  /// @brief e2e processor settings
  struct E2EProcessorSettings {
    E2ENet::E2ENetSettings e2e_net_settings;
  };

 public:
  /// @brief get single instance
  /// @return instance
  static E2EProcessor &GetInstance() noexcept;

  /// @brief get init status
  /// @return init flag
  inline bool IsInit() const noexcept { return is_init_; }

  /// @brief get run status
  /// @return run flag
  inline bool IsRun() const noexcept { return is_run_; }

  /// @brief get settings
  /// @return E2EProcessorSettings
  inline E2EProcessorSettings const &Settings() const noexcept {
    return settings_;
  }

  /// @brief do processor init
  /// @param err error message
  /// @return false on error
  void Init() noexcept(false);

  /// @brief start
  void Start() noexcept(false);

  /// @brief stop
  void Stop() noexcept;

 protected:
  E2EProcessor();

  E2EProcessorSettings gen_default() const noexcept;

  /// @brief
  /// @param e2eProcessorPtr
  static void main_task(E2EProcessor *e2eProcessorPtr) noexcept;

  /// @brief sleep for millisecond
  /// @param milliseconds
  void msleep(std::uint32_t milliseconds) noexcept {
    std::chrono::milliseconds dura{milliseconds};
    std::this_thread::sleep_for(dura);
  }

 private:
  /// @brief init flag
  bool is_init_;
  /// @brief run flag
  bool is_run_;
  /// @brief command to stop processor
  bool cmd_stop_;
  /// @brief locker
  std::mutex lock_;
  /// @brief  run call once flag
  mutable std::once_flag run_flag_;
  /// @brief  init call once flag
  mutable std::once_flag init_flag_;
  /// @brief settings
  E2EProcessorSettings settings_;
  /// @brief main task
  std::thread task_thread_;

  /// @brief perception result pointer
  PerceptionResultBufferPtr result_buffer_ptr_;

  /// @brief e2e net
  E2ENet e2e_net_;
};
}  // namespace perception
}  // namespace autodrive

namespace boost::json {
using namespace autodrive::perception;

inline void tag_invoke(value_from_tag, value &jv,
                       TrtInfer::TrtInferSettings const &s) {
  jv = {{"model_file", value_from(s.model_file)},
        {"use_cuda_device", value_from(s.use_cuda_device)},
        {"use_dla_core", value_from(s.use_dla_core)},
        {"max_batch_size", value_from(s.max_batch_size)},
        {"max_work_space_size", value_from(s.max_work_space_size)},
        {"scaler_type", value_from(s.scaler_type)}};
}

inline TrtInfer::TrtInferSettings tag_invoke(
    value_to_tag<TrtInfer::TrtInferSettings>, boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  TrtInfer::TrtInferSettings settings;
  settings.model_file = deserialize_obj<std::string>(obj, "model_file");
  settings.use_cuda_device =
      deserialize_obj<std::int32_t>(obj, "use_cuda_device");
  settings.use_dla_core = deserialize_obj<std::int32_t>(obj, "use_dla_core");
  settings.max_batch_size =
      deserialize_obj<std::int32_t>(obj, "max_batch_size");
  settings.max_work_space_size =
      deserialize_obj<std::int32_t>(obj, "max_work_space_size");
  settings.scaler_type = deserialize_obj<std::string>(obj, "scaler_type");

  return settings;
}

inline void tag_invoke(value_from_tag, value &jv,
                       E2ENet::E2ENetSettings const &s) {
  jv = {{"trt_infer_settings", value_from(s.trt_infer_settings)}};
}

inline E2ENet::E2ENetSettings tag_invoke(value_to_tag<E2ENet::E2ENetSettings>,
                                         value const &jv) {
  auto const &obj = jv.as_object();
  E2ENet::E2ENetSettings settings;
  settings.trt_infer_settings =
      deserialize_obj<TrtInfer::TrtInferSettings>(obj, "trt_infer_settings");
  return settings;
}

inline void tag_invoke(value_from_tag, value &jv,
                       E2EProcessor::E2EProcessorSettings const &s) {
  jv = {{"e2e_net_settings", value_from(s.e2e_net_settings)}};
}

inline E2EProcessor::E2EProcessorSettings tag_invoke(
    value_to_tag<E2EProcessor::E2EProcessorSettings>, value const &jv) {
  auto const &obj = jv.as_object();
  E2EProcessor::E2EProcessorSettings settings;
  settings.e2e_net_settings =
      deserialize_obj<E2ENet::E2ENetSettings>(obj, "e2e_net_settings");
  return settings;
}
}  // namespace boost::json

#endif  // E2E_PROCESSOR_H_
